﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="JobHandler.cs" company="DHGMS Solutions">
//   2004-2012 DHGMS Solutions. Some Rights Reserved. Licensed under GNU General Public License version 2 (GPLv2)
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Dhgms.TplHelper.Controller
{
    using System;
    using System.Configuration;
    using System.Diagnostics;
    using System.Reflection;
    using System.Threading;

    using Dhgms.DataManager.Model;
    using Dhgms.DataManager.Model.Exception;
    using Dhgms.DataManager.Model.Helper;
    using Dhgms.TplHelper.Controller.Job;
    using Dhgms.TplHelper.Model.Info;
    using Dhgms.TplHelper.Model.Info.JobResult;
    using Dhgms.TplHelper.Model.ProviderProxy;

    using NLog;
    using NLog.Config;
    using NLog.Targets;

    /// <summary>
    /// Generic class for an programs run, takes out the workload from error handling and reporting
    /// </summary>
    /// <typeparam name="TJobResultClass">
    /// Result information relating to a job
    /// </typeparam>
    /// <typeparam name="TJobClass">
    /// The class containing the processing logic for a job
    /// </typeparam>
    /// <typeparam name="TApplicationSettings">
    /// Application settings relating to a job
    /// </typeparam>
    public class JobHandler<TJobResultClass, TJobClass, TApplicationSettings> : IDisposable
        //where TInheritingClass : JobHandler<TInheritingClass, TJobResultClass, TJobClass, TApplicationSettings>, new()
        where TJobResultClass : Base, new()
        where TApplicationSettings : ConfigurationSection, IApplicationConfiguration
        where TJobClass : Base<TJobResultClass, TApplicationSettings>, new()
    {
        #region Constants and Fields

        /// <summary>
        /// The unique identifier for the job.
        /// </summary>
        private readonly Guid programGuid;

        /// <summary>
        /// The program name.
        /// </summary>
        private readonly string programName;

        /// <summary>
        /// The lazy loading provider proxy for job reporting.
        /// </summary>
        private readonly Lazy<Job<TJobResultClass>> lazyProviderProxy = new Lazy<Job<TJobResultClass>>(() => new Job<TJobResultClass>()); 

// ReSharper disable StaticFieldInGenericType
        // private static bool finished;
// ReSharper restore StaticFieldInGenericType

        /// <summary>
        /// The _mutex.
        /// </summary>
        private Mutex mutex;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="JobHandler{TJobResultClass,TJobClass,TApplicationSettings}"/> class. 
        /// </summary>
        /// <param name="programName">
        /// The program Name.
        /// </param>
        /// <param name="programGuid">
        /// The job unique identifier.
        /// </param>
        /// <param name="applicationInstanceMutexLock">
        /// The type of mutex lock to apply to the program
        /// </param>
        /// <param name="loggerFileTarget">
        /// The NLog File Target.
        /// </param>
        /// <param name="loggerDatabaseTarget">
        /// The NLog Database Target.
        /// </param>
        public JobHandler(string programName, Guid programGuid, ApplicationInstanceMutexLock applicationInstanceMutexLock, FileTarget loggerFileTarget, DatabaseTarget loggerDatabaseTarget)
        {
            this.ApplicationInstanceMutexLock = applicationInstanceMutexLock;
            this.programName = programName;
            this.programGuid = programGuid;

            // Step 1. Create configuration object 
            var config = new LoggingConfiguration();

            // Step 2. Create targets and add them to the configuration
            var consoleAttached = false;
            try
            {
                var temp = Console.Title;
                consoleAttached = !string.IsNullOrWhiteSpace(temp);
            }
            // ReSharper disable EmptyGeneralCatchClause
            catch
            // ReSharper restore EmptyGeneralCatchClause
            {
            }

            var debuggerAttached = Debugger.IsAttached;

            if (consoleAttached)
            {
                var consoleTarget = new ColoredConsoleTarget();
                config.AddTarget("console", consoleTarget);
                consoleTarget.Layout = @"${longdate} | ${level} | ${logger:shortName=true} | ${message}";
                config.LoggingRules.Add(new LoggingRule("*", debuggerAttached ? LogLevel.Trace : LogLevel.Info, consoleTarget));
            }

            if (debuggerAttached)
            {
                var debugTarget = new DebuggerTarget();
                config.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, debugTarget));
            }

            if (loggerFileTarget != null)
            {
                config.LoggingRules.Add(new LoggingRule("*", debuggerAttached ? LogLevel.Trace : LogLevel.Info, loggerFileTarget));
            }

            if (loggerDatabaseTarget != null)
            {
                config.LoggingRules.Add(new LoggingRule("*", debuggerAttached ? LogLevel.Trace : LogLevel.Info, loggerDatabaseTarget));
            }

            var logToReport = true;
            if (logToReport)
            {
                var memoryTarget = new MemoryTarget();
                config.LoggingRules.Add(new LoggingRule("*", LogLevel.Info, memoryTarget));
            }

            // Step 5. Activate the configuration
            LogManager.Configuration = config;

            Logger = LogManager.GetLogger("JobHandler");
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets how to apply a mutex lock to this process
        /// </summary>
        public ApplicationInstanceMutexLock ApplicationInstanceMutexLock { get; private set; }

        /// <summary>
        /// Gets the logger.
        /// </summary>
        protected static Logger Logger { get; private set; }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Entry point for disposing the object
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Entry point for doing the work
        /// </summary>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        public int Process()
        {
            Logger.Trace("Start of Job Handler.");

            // we don't want to catch and log exceptions if debugging
            // don't remove this conditional
            // it's here to prevent logging spam and to allow the debugger to work properly
            if (Debugger.IsAttached)
            {
                this.DoJob();
            }
            else
            {
                // not being debugged so we want to set up mini dump
                // we only use for minidumps
                // emails and reports are handled by the providers as we want to cater different reports
                // to different targets on the same channel
                DoWatcher();

                try
                {
                    this.DoJob();
                }
                catch (Exception e)
                {
                    Logger.ErrorException("Exception in Processing of Job", e);
                    if (e is OutOfMemoryException)
                    {
                        // oops
                        // try to get the app back into a state where it can definately produce notifications
                        GC.Collect();
                    }

                    try
                    {
                        // we catch in case the config file is screwed
                        this.lazyProviderProxy.Value.OnJobFailure(this.programGuid, this.programName, 0, e);
                    }
// ReSharper disable EmptyGeneralCatchClause
                    catch
// ReSharper restore EmptyGeneralCatchClause
                    {
                        // we want the original exception going to the console
                        // not the exception that it can't log the fault

                        // prevents the exception on failing to log
                        return 2;
                    }

                    return 1;
                }
            }

            Logger.Trace("End of Job Handler.");

            return 0;
        }

        #endregion

        #region Methods

        /// <summary>
        /// does the donkey work for disposing the object
        /// </summary>
        /// <param name="disposing">
        /// whether we're being disposed
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
            Logger.Trace("Start of Dispose.");

            if (!disposing)
            {
                return;
            }

            // dispose managed resources
            if (this.mutex != null)
            {
                this.mutex.ReleaseMutex();
            }

            // free native resources
            Logger.Trace("End of Dispose.");
        }

        /// <summary>
        /// Sets up the watcher for handling crash dumps
        /// </summary>
        private static void DoWatcher()
        {
            Logger.Trace("Start of DoWatcher.");

            Logger.Trace("End of DoWatcher.");
        }

        /// <summary>
        /// Does the actual work for running the task and handling the result
        /// </summary>
        private void DoJob()
        {
            Logger.Trace("Start of DoJob.");

            if (this.ApplicationInstanceMutexLock != ApplicationInstanceMutexLock.None)
            {
                // try to set up a mutex
                var mutexName = this.GetMutexName();
                try
                {
                    this.mutex = Mutex.OpenExisting(mutexName);

                    Logger.Error("The mutex for this job already exists. Indicating it is already running.");

                    // mutex already exists
                    // drop out
                    return;
                }
                catch (WaitHandleCannotBeOpenedException)
                {
                    this.mutex = new Mutex(true, mutexName);
                }
            }

            var job = new TJobClass();

            var settings = new Dhgms.DataManager.Model.ConfigurationLoader.XmlAppConfigConfigurationLoader<TApplicationSettings>().GetSettings();
            var jobResult = job.Execute(settings);

            // in future we'll thread the job so we can handle cancellations
            Logger.Info("Starting Job Success Notification.");
            this.lazyProviderProxy.Value.OnJobSucess(this.programGuid, this.programName, jobResult);
            Logger.Info("Finished Job Success Notification.");

            this.mutex.ReleaseMutex();

            Logger.Trace("End Of DoJob.");
        }

        /// <summary>
        /// Gets the mutex name that should be used for the mutex lock
        /// </summary>
        /// <returns>
        /// the mutex name that should be used for the mutex lock
        /// </returns>
        private string GetMutexName()
        {
            switch (this.ApplicationInstanceMutexLock)
            {
                case ApplicationInstanceMutexLock.PerSystemFullyQualifiedPath:
                    return "Global\\" + Assembly.GetExecutingAssembly().GetName().CodeBase;
                case ApplicationInstanceMutexLock.PerSystemGuid:
                    return "Global\\" + this.programGuid;
                case ApplicationInstanceMutexLock.PerUserFullyQualifiedPath:
                    return "Local\\" + Assembly.GetExecutingAssembly().GetName().CodeBase;
                case ApplicationInstanceMutexLock.PerUserGuid:
                    return "Local\\" + this.programGuid;
                default:
                    throw new EnumerationValueNotSupportedException("ApplicationInstanceMutexLock");
            }
        }

        #endregion
    }
}